/*-------------------------------------------------------------------------
 *
 * fe-secure-openssl.c
 *      OpenSSL support
 *
 *
 * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *      src/interfaces/libpq/fe-secure-openssl.c
 *
 * NOTES
 *
 *      We don't provide informational callbacks here (like
 *      info_cb() in be-secure.c), since there's no good mechanism to
 *      display such information to the user.
 *
 *-------------------------------------------------------------------------
 */

#include "postgres_fe.h"

#include <signal.h>
#include <fcntl.h>
#include <ctype.h>

#include "libpq-fe.h"
#include "fe-auth.h"
#include "libpq-int.h"

#ifdef WIN32
#include "win32.h"
#else
#include <sys/socket.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#endif
#include <arpa/inet.h>
#endif

#include <sys/stat.h>

#ifdef ENABLE_THREAD_SAFETY
#ifdef WIN32
#include "pthread-win32.h"
#else
#include <pthread.h>
#endif
#endif

#include <openssl/ssl.h>
#include <openssl/conf.h>
#ifdef USE_SSL_ENGINE
#include <openssl/engine.h>
#endif
#include <openssl/x509v3.h>

static bool verify_peer_name_matches_certificate(PGconn *);
static int    verify_cb(int ok, X509_STORE_CTX *ctx);
static int verify_peer_name_matches_certificate_name(PGconn *conn,
                                          ASN1_STRING *name,
                                          char **store_name);
static void destroy_ssl_system(void);
static int    initialize_SSL(PGconn *conn);
static PostgresPollingStatusType open_client_SSL(PGconn *);
static char *SSLerrmessage(unsigned long ecode);
static void SSLerrfree(char *buf);

static int    my_sock_read(BIO *h, char *buf, int size);
static int    my_sock_write(BIO *h, const char *buf, int size);
static BIO_METHOD *my_BIO_s_socket(void);
static int    my_SSL_set_fd(PGconn *conn, int fd);


static bool pq_init_ssl_lib = true;
static bool pq_init_crypto_lib = true;

static bool ssl_lib_initialized = false;

#ifdef ENABLE_THREAD_SAFETY
static long ssl_open_connections = 0;

#ifndef WIN32
static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
#else
static pthread_mutex_t ssl_config_mutex = NULL;
static long win32_ssl_create_mutex = 0;
#endif
#endif                            /* ENABLE_THREAD_SAFETY */


/* ------------------------------------------------------------ */
/*             Procedures common to all secure sessions            */
/* ------------------------------------------------------------ */

/*
 *    Exported function to allow application to tell us it's already
 *    initialized OpenSSL and/or libcrypto.
 */
void
pgtls_init_library(bool do_ssl, int do_crypto)
{
#ifdef ENABLE_THREAD_SAFETY

    /*
     * Disallow changing the flags while we have open connections, else we'd
     * get completely confused.
     */
    if (ssl_open_connections != 0)
        return;
#endif

    pq_init_ssl_lib = do_ssl;
    pq_init_crypto_lib = do_crypto;
}

/*
 *    Begin or continue negotiating a secure session.
 */
PostgresPollingStatusType
pgtls_open_client(PGconn *conn)
{
    /* First time through? */
    if (conn->ssl == NULL)
    {
        /*
         * Create a connection-specific SSL object, and load client
         * certificate, private key, and trusted CA certs.
         */
        if (initialize_SSL(conn) != 0)
        {
            /* initialize_SSL already put a message in conn->errorMessage */
            pgtls_close(conn);
            return PGRES_POLLING_FAILED;
        }
    }

    /* Begin or continue the actual handshake */
    return open_client_SSL(conn);
}

/*
 *    Is there unread data waiting in the SSL read buffer?
 */
bool
pgtls_read_pending(PGconn *conn)
{
    return SSL_pending(conn->ssl);
}

/*
 *    Read data from a secure connection.
 *
 * On failure, this function is responsible for putting a suitable message
 * into conn->errorMessage.  The caller must still inspect errno, but only
 * to determine whether to continue/retry after error.
 */
ssize_t
pgtls_read(PGconn *conn, void *ptr, size_t len)
{// #lizard forgives
    ssize_t        n;
    int            result_errno = 0;
    char        sebuf[256];
    int            err;
    unsigned long ecode;

rloop:

    /*
     * Prepare to call SSL_get_error() by clearing thread's OpenSSL error
     * queue.  In general, the current thread's error queue must be empty
     * before the TLS/SSL I/O operation is attempted, or SSL_get_error() will
     * not work reliably.  Since the possibility exists that other OpenSSL
     * clients running in the same thread but not under our control will fail
     * to call ERR_get_error() themselves (after their own I/O operations),
     * pro-actively clear the per-thread error queue now.
     */
    SOCK_ERRNO_SET(0);
    ERR_clear_error();
    n = SSL_read(conn->ssl, ptr, len);
    err = SSL_get_error(conn->ssl, n);

    /*
     * Other clients of OpenSSL may fail to call ERR_get_error(), but we
     * always do, so as to not cause problems for OpenSSL clients that don't
     * call ERR_clear_error() defensively.  Be sure that this happens by
     * calling now.  SSL_get_error() relies on the OpenSSL per-thread error
     * queue being intact, so this is the earliest possible point
     * ERR_get_error() may be called.
     */
    ecode = (err != SSL_ERROR_NONE || n < 0) ? ERR_get_error() : 0;
    switch (err)
    {
        case SSL_ERROR_NONE:
            if (n < 0)
            {
                /* Not supposed to happen, so we don't translate the msg */
                printfPQExpBuffer(&conn->errorMessage,
                                  "SSL_read failed but did not provide error information\n");
                /* assume the connection is broken */
                result_errno = ECONNRESET;
            }
            break;
        case SSL_ERROR_WANT_READ:
            n = 0;
            break;
        case SSL_ERROR_WANT_WRITE:

            /*
             * Returning 0 here would cause caller to wait for read-ready,
             * which is not correct since what SSL wants is wait for
             * write-ready.  The former could get us stuck in an infinite
             * wait, so don't risk it; busy-loop instead.
             */
            goto rloop;
        case SSL_ERROR_SYSCALL:
            if (n < 0)
            {
                result_errno = SOCK_ERRNO;
                if (result_errno == EPIPE ||
                    result_errno == ECONNRESET)
                    printfPQExpBuffer(&conn->errorMessage,
                                      libpq_gettext(
                                                    "server closed the connection unexpectedly\n"
                                                    "\tThis probably means the server terminated abnormally\n"
                                                    "\tbefore or while processing the request.\n"));
                else
                    printfPQExpBuffer(&conn->errorMessage,
                                      libpq_gettext("SSL SYSCALL error: %s\n"),
                                      SOCK_STRERROR(result_errno,
                                                    sebuf, sizeof(sebuf)));
            }
            else
            {
                printfPQExpBuffer(&conn->errorMessage,
                                  libpq_gettext("SSL SYSCALL error: EOF detected\n"));
                /* assume the connection is broken */
                result_errno = ECONNRESET;
                n = -1;
            }
            break;
        case SSL_ERROR_SSL:
            {
                char       *errm = SSLerrmessage(ecode);

                printfPQExpBuffer(&conn->errorMessage,
                                  libpq_gettext("SSL error: %s\n"), errm);
                SSLerrfree(errm);
                /* assume the connection is broken */
                result_errno = ECONNRESET;
                n = -1;
                break;
            }
        case SSL_ERROR_ZERO_RETURN:

            /*
             * Per OpenSSL documentation, this error code is only returned for
             * a clean connection closure, so we should not report it as a
             * server crash.
             */
            printfPQExpBuffer(&conn->errorMessage,
                              libpq_gettext("SSL connection has been closed unexpectedly\n"));
            result_errno = ECONNRESET;
            n = -1;
            break;
        default:
            printfPQExpBuffer(&conn->errorMessage,
                              libpq_gettext("unrecognized SSL error code: %d\n"),
                              err);
            /* assume the connection is broken */
            result_errno = ECONNRESET;
            n = -1;
            break;
    }

    /* ensure we return the intended errno to caller */
    SOCK_ERRNO_SET(result_errno);

    return n;
}

/*
 *    Write data to a secure connection.
 *
 * On failure, this function is responsible for putting a suitable message
 * into conn->errorMessage.  The caller must still inspect errno, but only
 * to determine whether to continue/retry after error.
 */
ssize_t
pgtls_write(PGconn *conn, const void *ptr, size_t len)
{// #lizard forgives
    ssize_t        n;
    int            result_errno = 0;
    char        sebuf[256];
    int            err;
    unsigned long ecode;

    SOCK_ERRNO_SET(0);
    ERR_clear_error();
    n = SSL_write(conn->ssl, ptr, len);
    err = SSL_get_error(conn->ssl, n);
    ecode = (err != SSL_ERROR_NONE || n < 0) ? ERR_get_error() : 0;
    switch (err)
    {
        case SSL_ERROR_NONE:
            if (n < 0)
            {
                /* Not supposed to happen, so we don't translate the msg */
                printfPQExpBuffer(&conn->errorMessage,
                                  "SSL_write failed but did not provide error information\n");
                /* assume the connection is broken */
                result_errno = ECONNRESET;
            }
            break;
        case SSL_ERROR_WANT_READ:

            /*
             * Returning 0 here causes caller to wait for write-ready, which
             * is not really the right thing, but it's the best we can do.
             */
            n = 0;
            break;
        case SSL_ERROR_WANT_WRITE:
            n = 0;
            break;
        case SSL_ERROR_SYSCALL:
            if (n < 0)
            {
                result_errno = SOCK_ERRNO;
                if (result_errno == EPIPE || result_errno == ECONNRESET)
                    printfPQExpBuffer(&conn->errorMessage,
                                      libpq_gettext(
                                                    "server closed the connection unexpectedly\n"
                                                    "\tThis probably means the server terminated abnormally\n"
                                                    "\tbefore or while processing the request.\n"));
                else
                    printfPQExpBuffer(&conn->errorMessage,
                                      libpq_gettext("SSL SYSCALL error: %s\n"),
                                      SOCK_STRERROR(result_errno,
                                                    sebuf, sizeof(sebuf)));
            }
            else
            {
                printfPQExpBuffer(&conn->errorMessage,
                                  libpq_gettext("SSL SYSCALL error: EOF detected\n"));
                /* assume the connection is broken */
                result_errno = ECONNRESET;
                n = -1;
            }
            break;
        case SSL_ERROR_SSL:
            {
                char       *errm = SSLerrmessage(ecode);

                printfPQExpBuffer(&conn->errorMessage,
                                  libpq_gettext("SSL error: %s\n"), errm);
                SSLerrfree(errm);
                /* assume the connection is broken */
                result_errno = ECONNRESET;
                n = -1;
                break;
            }
        case SSL_ERROR_ZERO_RETURN:

            /*
             * Per OpenSSL documentation, this error code is only returned for
             * a clean connection closure, so we should not report it as a
             * server crash.
             */
            printfPQExpBuffer(&conn->errorMessage,
                              libpq_gettext("SSL connection has been closed unexpectedly\n"));
            result_errno = ECONNRESET;
            n = -1;
            break;
        default:
            printfPQExpBuffer(&conn->errorMessage,
                              libpq_gettext("unrecognized SSL error code: %d\n"),
                              err);
            /* assume the connection is broken */
            result_errno = ECONNRESET;
            n = -1;
            break;
    }

    /* ensure we return the intended errno to caller */
    SOCK_ERRNO_SET(result_errno);

    return n;
}

/* ------------------------------------------------------------ */
/*                        OpenSSL specific code                    */
/* ------------------------------------------------------------ */

/*
 *    Certificate verification callback
 *
 *    This callback allows us to log intermediate problems during
 *    verification, but there doesn't seem to be a clean way to get
 *    our PGconn * structure.  So we can't log anything!
 *
 *    This callback also allows us to override the default acceptance
 *    criteria (e.g., accepting self-signed or expired certs), but
 *    for now we accept the default checks.
 */
static int
verify_cb(int ok, X509_STORE_CTX *ctx)
{
    return ok;
}


/*
 * Check if a wildcard certificate matches the server hostname.
 *
 * The rule for this is:
 *    1. We only match the '*' character as wildcard
 *    2. We match only wildcards at the start of the string
 *    3. The '*' character does *not* match '.', meaning that we match only
 *       a single pathname component.
 *    4. We don't support more than one '*' in a single pattern.
 *
 * This is roughly in line with RFC2818, but contrary to what most browsers
 * appear to be implementing (point 3 being the difference)
 *
 * Matching is always case-insensitive, since DNS is case insensitive.
 */
static int
wildcard_certificate_match(const char *pattern, const char *string)
{
    int            lenpat = strlen(pattern);
    int            lenstr = strlen(string);

    /* If we don't start with a wildcard, it's not a match (rule 1 & 2) */
    if (lenpat < 3 ||
        pattern[0] != '*' ||
        pattern[1] != '.')
        return 0;

    if (lenpat > lenstr)
        /* If pattern is longer than the string, we can never match */
        return 0;

    if (pg_strcasecmp(pattern + 1, string + lenstr - lenpat + 1) != 0)

        /*
         * If string does not end in pattern (minus the wildcard), we don't
         * match
         */
        return 0;

    if (strchr(string, '.') < string + lenstr - lenpat)

        /*
         * If there is a dot left of where the pattern started to match, we
         * don't match (rule 3)
         */
        return 0;

    /* String ended with pattern, and didn't have a dot before, so we match */
    return 1;
}

/*
 * Check if a name from a server's certificate matches the peer's hostname.
 *
 * Returns 1 if the name matches, and 0 if it does not. On error, returns
 * -1, and sets the libpq error message.
 *
 * The name extracted from the certificate is returned in *store_name. The
 * caller is responsible for freeing it.
 */
static int
verify_peer_name_matches_certificate_name(PGconn *conn, ASN1_STRING *name_entry,
                                          char **store_name)
{
    int            len;
    char       *name;
    const unsigned char *namedata;
    int            result;
    char       *host = PQhost(conn);

    *store_name = NULL;

    /* Should not happen... */
    if (name_entry == NULL)
    {
        printfPQExpBuffer(&conn->errorMessage,
                          libpq_gettext("SSL certificate's name entry is missing\n"));
        return -1;
    }

    /*
     * GEN_DNS can be only IA5String, equivalent to US ASCII.
     *
     * There is no guarantee the string returned from the certificate is
     * NULL-terminated, so make a copy that is.
     */
#ifdef HAVE_ASN1_STRING_GET0_DATA
    namedata = ASN1_STRING_get0_data(name_entry);
#else
    namedata = ASN1_STRING_data(name_entry);
#endif
    len = ASN1_STRING_length(name_entry);
    name = malloc(len + 1);
    if (name == NULL)
    {
        printfPQExpBuffer(&conn->errorMessage,
                          libpq_gettext("out of memory\n"));
        return -1;
    }
    memcpy(name, namedata, len);
    name[len] = '\0';

    /*
     * Reject embedded NULLs in certificate common or alternative name to
     * prevent attacks like CVE-2009-4034.
     */
    if (len != strlen(name))
    {
        free(name);
        printfPQExpBuffer(&conn->errorMessage,
                          libpq_gettext("SSL certificate's name contains embedded null\n"));
        return -1;
    }

    if (pg_strcasecmp(name, host) == 0)
    {
        /* Exact name match */
        result = 1;
    }
    else if (wildcard_certificate_match(name, host))
    {
        /* Matched wildcard name */
        result = 1;
    }
    else
    {
        result = 0;
    }

    *store_name = name;
    return result;
}

/*
 *    Verify that the server certificate matches the hostname we connected to.
 *
 * The certificate's Common Name and Subject Alternative Names are considered.
 */
static bool
verify_peer_name_matches_certificate(PGconn *conn)
{// #lizard forgives
    int            names_examined = 0;
    bool        found_match = false;
    bool        got_error = false;
    char       *first_name = NULL;

    STACK_OF(GENERAL_NAME) *peer_san;
    int            i;
    int            rc;
    char       *host = PQhost(conn);

    /*
     * If told not to verify the peer name, don't do it. Return true
     * indicating that the verification was successful.
     */
    if (strcmp(conn->sslmode, "verify-full") != 0)
        return true;

    /* Check that we have a hostname to compare with. */
    if (!(host && host[0] != '\0'))
    {
        printfPQExpBuffer(&conn->errorMessage,
                          libpq_gettext("host name must be specified for a verified SSL connection\n"));
        return false;
    }

    /*
     * First, get the Subject Alternative Names (SANs) from the certificate,
     * and compare them against the originally given hostname.
     */
    peer_san = (STACK_OF(GENERAL_NAME) *)
        X509_get_ext_d2i(conn->peer, NID_subject_alt_name, NULL, NULL);

    if (peer_san)
    {
        int            san_len = sk_GENERAL_NAME_num(peer_san);

        for (i = 0; i < san_len; i++)
        {
            const GENERAL_NAME *name = sk_GENERAL_NAME_value(peer_san, i);

            if (name->type == GEN_DNS)
            {
                char       *alt_name;

                names_examined++;
                rc = verify_peer_name_matches_certificate_name(conn,
                                                               name->d.dNSName,
                                                               &alt_name);
                if (rc == -1)
                    got_error = true;
                if (rc == 1)
                    found_match = true;

                if (alt_name)
                {
                    if (!first_name)
                        first_name = alt_name;
                    else
                        free(alt_name);
                }
            }
            if (found_match || got_error)
                break;
        }
        sk_GENERAL_NAME_free(peer_san);
    }

    /*
     * If there is no subjectAltName extension of type dNSName, check the
     * Common Name.
     *
     * (Per RFC 2818 and RFC 6125, if the subjectAltName extension of type
     * dNSName is present, the CN must be ignored.)
     */
    if (names_examined == 0)
    {
        X509_NAME  *subject_name;

        subject_name = X509_get_subject_name(conn->peer);
        if (subject_name != NULL)
        {
            int            cn_index;

            cn_index = X509_NAME_get_index_by_NID(subject_name,
                                                  NID_commonName, -1);
            if (cn_index >= 0)
            {
                names_examined++;
                rc = verify_peer_name_matches_certificate_name(
                                                               conn,
                                                               X509_NAME_ENTRY_get_data(
                                                                                        X509_NAME_get_entry(subject_name, cn_index)),
                                                               &first_name);

                if (rc == -1)
                    got_error = true;
                else if (rc == 1)
                    found_match = true;
            }
        }
    }

    if (!found_match && !got_error)
    {
        /*
         * No match. Include the name from the server certificate in the error
         * message, to aid debugging broken configurations. If there are
         * multiple names, only print the first one to avoid an overly long
         * error message.
         */
        if (names_examined > 1)
        {
            printfPQExpBuffer(&conn->errorMessage,
                              libpq_ngettext("server certificate for \"%s\" (and %d other name) does not match host name \"%s\"\n",
                                             "server certificate for \"%s\" (and %d other names) does not match host name \"%s\"\n",
                                             names_examined - 1),
                              first_name, names_examined - 1, host);
        }
        else if (names_examined == 1)
        {
            printfPQExpBuffer(&conn->errorMessage,
                              libpq_gettext("server certificate for \"%s\" does not match host name \"%s\"\n"),
                              first_name, host);
        }
        else
        {
            printfPQExpBuffer(&conn->errorMessage,
                              libpq_gettext("could not get server's host name from server certificate\n"));
        }
    }

    /* clean up */
    if (first_name)
        free(first_name);

    return found_match && !got_error;
}

#if defined(ENABLE_THREAD_SAFETY) && defined(HAVE_CRYPTO_LOCK)
/*
 *    Callback functions for OpenSSL internal locking.  (OpenSSL 1.1.0
 *    does its own locking, and doesn't need these anymore.  The
 *    CRYPTO_lock() function was removed in 1.1.0, when the callbacks
 *    were made obsolete, so we assume that if CRYPTO_lock() exists,
 *    the callbacks are still required.)
 */

static unsigned long
pq_threadidcallback(void)
{
    /*
     * This is not standards-compliant.  pthread_self() returns pthread_t, and
     * shouldn't be cast to unsigned long, but CRYPTO_set_id_callback requires
     * it, so we have to do it.
     */
    return (unsigned long) pthread_self();
}

static pthread_mutex_t *pq_lockarray;

static void
pq_lockingcallback(int mode, int n, const char *file, int line)
{
    if (mode & CRYPTO_LOCK)
    {
        if (pthread_mutex_lock(&pq_lockarray[n]))
            PGTHREAD_ERROR("failed to lock mutex");
    }
    else
    {
        if (pthread_mutex_unlock(&pq_lockarray[n]))
            PGTHREAD_ERROR("failed to unlock mutex");
    }
}
#endif                            /* ENABLE_THREAD_SAFETY && HAVE_CRYPTO_LOCK */

/*
 * Initialize SSL library.
 *
 * In threadsafe mode, this includes setting up libcrypto callback functions
 * to do thread locking.
 *
 * If the caller has told us (through PQinitOpenSSL) that he's taking care
 * of libcrypto, we expect that callbacks are already set, and won't try to
 * override it.
 *
 * The conn parameter is only used to be able to pass back an error
 * message - no connection-local setup is made here.
 *
 * Returns 0 if OK, -1 on failure (with a message in conn->errorMessage).
 */
int
pgtls_init(PGconn *conn)
{// #lizard forgives
#ifdef ENABLE_THREAD_SAFETY
#ifdef WIN32
    /* Also see similar code in fe-connect.c, default_threadlock() */
    if (ssl_config_mutex == NULL)
    {
        while (InterlockedExchange(&win32_ssl_create_mutex, 1) == 1)
             /* loop, another thread own the lock */ ;
        if (ssl_config_mutex == NULL)
        {
            if (pthread_mutex_init(&ssl_config_mutex, NULL))
                return -1;
        }
        InterlockedExchange(&win32_ssl_create_mutex, 0);
    }
#endif
    if (pthread_mutex_lock(&ssl_config_mutex))
        return -1;

#ifdef HAVE_CRYPTO_LOCK
    if (pq_init_crypto_lib)
    {
        /*
         * If necessary, set up an array to hold locks for libcrypto.
         * libcrypto will tell us how big to make this array.
         */
        if (pq_lockarray == NULL)
        {
            int            i;

            pq_lockarray = malloc(sizeof(pthread_mutex_t) * CRYPTO_num_locks());
            if (!pq_lockarray)
            {
                pthread_mutex_unlock(&ssl_config_mutex);
                return -1;
            }
            for (i = 0; i < CRYPTO_num_locks(); i++)
            {
                if (pthread_mutex_init(&pq_lockarray[i], NULL))
                {
                    free(pq_lockarray);
                    pq_lockarray = NULL;
                    pthread_mutex_unlock(&ssl_config_mutex);
                    return -1;
                }
            }
        }

        if (ssl_open_connections++ == 0)
        {
            /*
             * These are only required for threaded libcrypto applications,
             * but make sure we don't stomp on them if they're already set.
             */
            if (CRYPTO_get_id_callback() == NULL)
                CRYPTO_set_id_callback(pq_threadidcallback);
            if (CRYPTO_get_locking_callback() == NULL)
                CRYPTO_set_locking_callback(pq_lockingcallback);
        }
    }
#endif                            /* HAVE_CRYPTO_LOCK */
#endif                            /* ENABLE_THREAD_SAFETY */

    if (!ssl_lib_initialized)
    {
        if (pq_init_ssl_lib)
        {
#ifdef HAVE_OPENSSL_INIT_SSL
            OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, NULL);
#else
            OPENSSL_config(NULL);
            SSL_library_init();
            SSL_load_error_strings();
#endif
        }
        ssl_lib_initialized = true;
    }

#ifdef ENABLE_THREAD_SAFETY
    pthread_mutex_unlock(&ssl_config_mutex);
#endif
    return 0;
}

/*
 *    This function is needed because if the libpq library is unloaded
 *    from the application, the callback functions will no longer exist when
 *    libcrypto is used by other parts of the system.  For this reason,
 *    we unregister the callback functions when the last libpq
 *    connection is closed.  (The same would apply for OpenSSL callbacks
 *    if we had any.)
 *
 *    Callbacks are only set when we're compiled in threadsafe mode, so
 *    we only need to remove them in this case. They are also not needed
 *    with OpenSSL 1.1.0 anymore.
 */
static void
destroy_ssl_system(void)
{// #lizard forgives
#if defined(ENABLE_THREAD_SAFETY) && defined(HAVE_CRYPTO_LOCK)
    /* Mutex is created in initialize_ssl_system() */
    if (pthread_mutex_lock(&ssl_config_mutex))
        return;

    if (pq_init_crypto_lib && ssl_open_connections > 0)
        --ssl_open_connections;

    if (pq_init_crypto_lib && ssl_open_connections == 0)
    {
        /*
         * No connections left, unregister libcrypto callbacks, if no one
         * registered different ones in the meantime.
         */
        if (CRYPTO_get_locking_callback() == pq_lockingcallback)
            CRYPTO_set_locking_callback(NULL);
        if (CRYPTO_get_id_callback() == pq_threadidcallback)
            CRYPTO_set_id_callback(NULL);

        /*
         * We don't free the lock array. If we get another connection in this
         * process, we will just re-use them with the existing mutexes.
         *
         * This means we leak a little memory on repeated load/unload of the
         * library.
         */
    }

    pthread_mutex_unlock(&ssl_config_mutex);
#endif
}

/*
 *    Create per-connection SSL object, and load the client certificate,
 *    private key, and trusted CA certs.
 *
 *    Returns 0 if OK, -1 on failure (with a message in conn->errorMessage).
 */
static int
initialize_SSL(PGconn *conn)
{// #lizard forgives
    SSL_CTX    *SSL_context;
    struct stat buf;
    char        homedir[MAXPGPATH];
    char        fnbuf[MAXPGPATH];
    char        sebuf[256];
    bool        have_homedir;
    bool        have_cert;
    bool        have_rootcert;
    EVP_PKEY   *pkey = NULL;

    /*
     * We'll need the home directory if any of the relevant parameters are
     * defaulted.  If pqGetHomeDirectory fails, act as though none of the
     * files could be found.
     */
    if (!(conn->sslcert && strlen(conn->sslcert) > 0) ||
        !(conn->sslkey && strlen(conn->sslkey) > 0) ||
        !(conn->sslrootcert && strlen(conn->sslrootcert) > 0) ||
        !(conn->sslcrl && strlen(conn->sslcrl) > 0))
        have_homedir = pqGetHomeDirectory(homedir, sizeof(homedir));
    else                        /* won't need it */
        have_homedir = false;

    /*
     * Create a new SSL_CTX object.
     *
     * We used to share a single SSL_CTX between all connections, but it was
     * complicated if connections used different certificates. So now we
     * create a separate context for each connection, and accept the overhead.
     */
    SSL_context = SSL_CTX_new(SSLv23_method());
    if (!SSL_context)
    {
        char       *err = SSLerrmessage(ERR_get_error());

        printfPQExpBuffer(&conn->errorMessage,
                          libpq_gettext("could not create SSL context: %s\n"),
                          err);
        SSLerrfree(err);
        return -1;
    }

    /* Disable old protocol versions */
    SSL_CTX_set_options(SSL_context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);

    /*
     * Disable OpenSSL's moving-write-buffer sanity check, because it causes
     * unnecessary failures in nonblocking send cases.
     */
    SSL_CTX_set_mode(SSL_context, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);

    /*
     * If the root cert file exists, load it so we can perform certificate
     * verification. If sslmode is "verify-full" we will also do further
     * verification after the connection has been completed.
     */
    if (conn->sslrootcert && strlen(conn->sslrootcert) > 0)
        strlcpy(fnbuf, conn->sslrootcert, sizeof(fnbuf));
    else if (have_homedir)
        snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CERT_FILE);
    else
        fnbuf[0] = '\0';

    if (fnbuf[0] != '\0' &&
        stat(fnbuf, &buf) == 0)
    {
        X509_STORE *cvstore;

        if (SSL_CTX_load_verify_locations(SSL_context, fnbuf, NULL) != 1)
        {
            char       *err = SSLerrmessage(ERR_get_error());

            printfPQExpBuffer(&conn->errorMessage,
                              libpq_gettext("could not read root certificate file \"%s\": %s\n"),
                              fnbuf, err);
            SSLerrfree(err);
            SSL_CTX_free(SSL_context);
            return -1;
        }

        if ((cvstore = SSL_CTX_get_cert_store(SSL_context)) != NULL)
        {
            if (conn->sslcrl && strlen(conn->sslcrl) > 0)
                strlcpy(fnbuf, conn->sslcrl, sizeof(fnbuf));
            else if (have_homedir)
                snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CRL_FILE);
            else
                fnbuf[0] = '\0';

            /* Set the flags to check against the complete CRL chain */
            if (fnbuf[0] != '\0' &&
                X509_STORE_load_locations(cvstore, fnbuf, NULL) == 1)
            {
                /* OpenSSL 0.96 does not support X509_V_FLAG_CRL_CHECK */
#ifdef X509_V_FLAG_CRL_CHECK
                X509_STORE_set_flags(cvstore,
                                     X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
#else
                char       *err = SSLerrmessage(ERR_get_error());

                printfPQExpBuffer(&conn->errorMessage,
                                  libpq_gettext("SSL library does not support CRL certificates (file \"%s\")\n"),
                                  fnbuf);
                SSLerrfree(err);
                SSL_CTX_free(SSL_context);
                return -1;
#endif
            }
            /* if not found, silently ignore;  we do not require CRL */
            ERR_clear_error();
        }
        have_rootcert = true;
    }
    else
    {
        /*
         * stat() failed; assume root file doesn't exist.  If sslmode is
         * verify-ca or verify-full, this is an error.  Otherwise, continue
         * without performing any server cert verification.
         */
        if (conn->sslmode[0] == 'v')    /* "verify-ca" or "verify-full" */
        {
            /*
             * The only way to reach here with an empty filename is if
             * pqGetHomeDirectory failed.  That's a sufficiently unusual case
             * that it seems worth having a specialized error message for it.
             */
            if (fnbuf[0] == '\0')
                printfPQExpBuffer(&conn->errorMessage,
                                  libpq_gettext("could not get home directory to locate root certificate file\n"
                                                "Either provide the file or change sslmode to disable server certificate verification.\n"));
            else
                printfPQExpBuffer(&conn->errorMessage,
                                  libpq_gettext("root certificate file \"%s\" does not exist\n"
                                                "Either provide the file or change sslmode to disable server certificate verification.\n"), fnbuf);
            SSL_CTX_free(SSL_context);
            return -1;
        }
        have_rootcert = false;
    }

    /* Read the client certificate file */
    if (conn->sslcert && strlen(conn->sslcert) > 0)
        strlcpy(fnbuf, conn->sslcert, sizeof(fnbuf));
    else if (have_homedir)
        snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_CERT_FILE);
    else
        fnbuf[0] = '\0';

    if (fnbuf[0] == '\0')
    {
        /* no home directory, proceed without a client cert */
        have_cert = false;
    }
    else if (stat(fnbuf, &buf) != 0)
    {
        /*
         * If file is not present, just go on without a client cert; server
         * might or might not accept the connection.  Any other error,
         * however, is grounds for complaint.
         */
        if (errno != ENOENT && errno != ENOTDIR)
        {
            printfPQExpBuffer(&conn->errorMessage,
                              libpq_gettext("could not open certificate file \"%s\": %s\n"),
                              fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf)));
            SSL_CTX_free(SSL_context);
            return -1;
        }
        have_cert = false;
    }
    else
    {
        /*
         * Cert file exists, so load it. Since OpenSSL doesn't provide the
         * equivalent of "SSL_use_certificate_chain_file", we have to load it
         * into the SSL context, rather than the SSL object.
         */
        if (SSL_CTX_use_certificate_chain_file(SSL_context, fnbuf) != 1)
        {
            char       *err = SSLerrmessage(ERR_get_error());

            printfPQExpBuffer(&conn->errorMessage,
                              libpq_gettext("could not read certificate file \"%s\": %s\n"),
                              fnbuf, err);
            SSLerrfree(err);
            SSL_CTX_free(SSL_context);
            return -1;
        }

        /* need to load the associated private key, too */
        have_cert = true;
    }

    /*
     * The SSL context is now loaded with the correct root and client
     * certificates. Create a connection-specific SSL object. The private key
     * is loaded directly into the SSL object. (We could load the private key
     * into the context, too, but we have done it this way historically, and
     * it doesn't really matter.)
     */
    if (!(conn->ssl = SSL_new(SSL_context)) ||
        !SSL_set_app_data(conn->ssl, conn) ||
        !my_SSL_set_fd(conn, conn->sock))
    {
        char       *err = SSLerrmessage(ERR_get_error());

        printfPQExpBuffer(&conn->errorMessage,
                          libpq_gettext("could not establish SSL connection: %s\n"),
                          err);
        SSLerrfree(err);
        SSL_CTX_free(SSL_context);
        return -1;
    }
    conn->ssl_in_use = true;

    /*
     * SSL contexts are reference counted by OpenSSL. We can free it as soon
     * as we have created the SSL object, and it will stick around for as long
     * as it's actually needed.
     */
    SSL_CTX_free(SSL_context);
    SSL_context = NULL;

    /*
     * Read the SSL key. If a key is specified, treat it as an engine:key
     * combination if there is colon present - we don't support files with
     * colon in the name. The exception is if the second character is a colon,
     * in which case it can be a Windows filename with drive specification.
     */
    if (have_cert && conn->sslkey && strlen(conn->sslkey) > 0)
    {
#ifdef USE_SSL_ENGINE
        if (strchr(conn->sslkey, ':')
#ifdef WIN32
            && conn->sslkey[1] != ':'
#endif
            )
        {
            /* Colon, but not in second character, treat as engine:key */
            char       *engine_str = strdup(conn->sslkey);
            char       *engine_colon;

            if (engine_str == NULL)
            {
                printfPQExpBuffer(&conn->errorMessage,
                                  libpq_gettext("out of memory\n"));
                return -1;
            }

            /* cannot return NULL because we already checked before strdup */
            engine_colon = strchr(engine_str, ':');

            *engine_colon = '\0';    /* engine_str now has engine name */
            engine_colon++;        /* engine_colon now has key name */

            conn->engine = ENGINE_by_id(engine_str);
            if (conn->engine == NULL)
            {
                char       *err = SSLerrmessage(ERR_get_error());

                printfPQExpBuffer(&conn->errorMessage,
                                  libpq_gettext("could not load SSL engine \"%s\": %s\n"),
                                  engine_str, err);
                SSLerrfree(err);
                free(engine_str);
                return -1;
            }

            if (ENGINE_init(conn->engine) == 0)
            {
                char       *err = SSLerrmessage(ERR_get_error());

                printfPQExpBuffer(&conn->errorMessage,
                                  libpq_gettext("could not initialize SSL engine \"%s\": %s\n"),
                                  engine_str, err);
                SSLerrfree(err);
                ENGINE_free(conn->engine);
                conn->engine = NULL;
                free(engine_str);
                return -1;
            }

            pkey = ENGINE_load_private_key(conn->engine, engine_colon,
                                           NULL, NULL);
            if (pkey == NULL)
            {
                char       *err = SSLerrmessage(ERR_get_error());

                printfPQExpBuffer(&conn->errorMessage,
                                  libpq_gettext("could not read private SSL key \"%s\" from engine \"%s\": %s\n"),
                                  engine_colon, engine_str, err);
                SSLerrfree(err);
                ENGINE_finish(conn->engine);
                ENGINE_free(conn->engine);
                conn->engine = NULL;
                free(engine_str);
                return -1;
            }
            if (SSL_use_PrivateKey(conn->ssl, pkey) != 1)
            {
                char       *err = SSLerrmessage(ERR_get_error());

                printfPQExpBuffer(&conn->errorMessage,
                                  libpq_gettext("could not load private SSL key \"%s\" from engine \"%s\": %s\n"),
                                  engine_colon, engine_str, err);
                SSLerrfree(err);
                ENGINE_finish(conn->engine);
                ENGINE_free(conn->engine);
                conn->engine = NULL;
                free(engine_str);
                return -1;
            }

            free(engine_str);

            fnbuf[0] = '\0';    /* indicate we're not going to load from a
                                 * file */
        }
        else
#endif                            /* USE_SSL_ENGINE */
        {
            /* PGSSLKEY is not an engine, treat it as a filename */
            strlcpy(fnbuf, conn->sslkey, sizeof(fnbuf));
        }
    }
    else if (have_homedir)
    {
        /* No PGSSLKEY specified, load default file */
        snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_KEY_FILE);
    }
    else
        fnbuf[0] = '\0';

    if (have_cert && fnbuf[0] != '\0')
    {
        /* read the client key from file */

        if (stat(fnbuf, &buf) != 0)
        {
            printfPQExpBuffer(&conn->errorMessage,
                              libpq_gettext("certificate present, but not private key file \"%s\"\n"),
                              fnbuf);
            return -1;
        }
#ifndef WIN32
        if (!S_ISREG(buf.st_mode) || buf.st_mode & (S_IRWXG | S_IRWXO))
        {
            printfPQExpBuffer(&conn->errorMessage,
                              libpq_gettext("private key file \"%s\" has group or world access; permissions should be u=rw (0600) or less\n"),
                              fnbuf);
            return -1;
        }
#endif

        if (SSL_use_PrivateKey_file(conn->ssl, fnbuf, SSL_FILETYPE_PEM) != 1)
        {
            char       *err = SSLerrmessage(ERR_get_error());

            printfPQExpBuffer(&conn->errorMessage,
                              libpq_gettext("could not load private key file \"%s\": %s\n"),
                              fnbuf, err);
            SSLerrfree(err);
            return -1;
        }
    }

    /* verify that the cert and key go together */
    if (have_cert &&
        SSL_check_private_key(conn->ssl) != 1)
    {
        char       *err = SSLerrmessage(ERR_get_error());

        printfPQExpBuffer(&conn->errorMessage,
                          libpq_gettext("certificate does not match private key file \"%s\": %s\n"),
                          fnbuf, err);
        SSLerrfree(err);
        return -1;
    }

    /*
     * If a root cert was loaded, also set our certificate verification
     * callback.
     */
    if (have_rootcert)
        SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, verify_cb);

    /*
     * If the OpenSSL version used supports it (from 1.0.0 on) and the user
     * requested it, disable SSL compression.
     */
#ifdef SSL_OP_NO_COMPRESSION
    if (conn->sslcompression && conn->sslcompression[0] == '0')
    {
        SSL_set_options(conn->ssl, SSL_OP_NO_COMPRESSION);
    }
#endif

    return 0;
}

/*
 *    Attempt to negotiate SSL connection.
 */
static PostgresPollingStatusType
open_client_SSL(PGconn *conn)
{// #lizard forgives
    int            r;

    ERR_clear_error();
    r = SSL_connect(conn->ssl);
    if (r <= 0)
    {
        int            err = SSL_get_error(conn->ssl, r);
        unsigned long ecode;

        ecode = ERR_get_error();
        switch (err)
        {
            case SSL_ERROR_WANT_READ:
                return PGRES_POLLING_READING;

            case SSL_ERROR_WANT_WRITE:
                return PGRES_POLLING_WRITING;

            case SSL_ERROR_SYSCALL:
                {
                    char        sebuf[256];

                    if (r == -1)
                        printfPQExpBuffer(&conn->errorMessage,
                                          libpq_gettext("SSL SYSCALL error: %s\n"),
                                          SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
                    else
                        printfPQExpBuffer(&conn->errorMessage,
                                          libpq_gettext("SSL SYSCALL error: EOF detected\n"));
                    pgtls_close(conn);
                    return PGRES_POLLING_FAILED;
                }
            case SSL_ERROR_SSL:
                {
                    char       *err = SSLerrmessage(ecode);

                    printfPQExpBuffer(&conn->errorMessage,
                                      libpq_gettext("SSL error: %s\n"),
                                      err);
                    SSLerrfree(err);
                    pgtls_close(conn);
                    return PGRES_POLLING_FAILED;
                }

            default:
                printfPQExpBuffer(&conn->errorMessage,
                                  libpq_gettext("unrecognized SSL error code: %d\n"),
                                  err);
                pgtls_close(conn);
                return PGRES_POLLING_FAILED;
        }
    }

    /*
     * We already checked the server certificate in initialize_SSL() using
     * SSL_CTX_set_verify(), if root.crt exists.
     */

    /* get server certificate */
    conn->peer = SSL_get_peer_certificate(conn->ssl);
    if (conn->peer == NULL)
    {
        char       *err;

        err = SSLerrmessage(ERR_get_error());

        printfPQExpBuffer(&conn->errorMessage,
                          libpq_gettext("certificate could not be obtained: %s\n"),
                          err);
        SSLerrfree(err);
        pgtls_close(conn);
        return PGRES_POLLING_FAILED;
    }

    if (!verify_peer_name_matches_certificate(conn))
    {
        pgtls_close(conn);
        return PGRES_POLLING_FAILED;
    }

    /* SSL handshake is complete */
    return PGRES_POLLING_OK;
}

/*
 *    Close SSL connection.
 */
void
pgtls_close(PGconn *conn)
{
    bool        destroy_needed = false;

    if (conn->ssl)
    {
        /*
         * We can't destroy everything SSL-related here due to the possible
         * later calls to OpenSSL routines which may need our thread
         * callbacks, so set a flag here and check at the end.
         */
        destroy_needed = true;

        SSL_shutdown(conn->ssl);
        SSL_free(conn->ssl);
        conn->ssl = NULL;
        conn->ssl_in_use = false;
    }

    if (conn->peer)
    {
        X509_free(conn->peer);
        conn->peer = NULL;
    }

#ifdef USE_SSL_ENGINE
    if (conn->engine)
    {
        ENGINE_finish(conn->engine);
        ENGINE_free(conn->engine);
        conn->engine = NULL;
    }
#endif

    /*
     * This will remove our SSL locking hooks, if this is the last SSL
     * connection, which means we must wait to call it until after all SSL
     * calls have been made, otherwise we can end up with a race condition and
     * possible deadlocks.
     *
     * See comments above destroy_ssl_system().
     */
    if (destroy_needed)
        destroy_ssl_system();
}


/*
 * Obtain reason string for passed SSL errcode
 *
 * ERR_get_error() is used by caller to get errcode to pass here.
 *
 * Some caution is needed here since ERR_reason_error_string will
 * return NULL if it doesn't recognize the error code.  We don't
 * want to return NULL ever.
 */
static char ssl_nomem[] = "out of memory allocating error description";

#define SSL_ERR_LEN 128

static char *
SSLerrmessage(unsigned long ecode)
{
    const char *errreason;
    char       *errbuf;

    errbuf = malloc(SSL_ERR_LEN);
    if (!errbuf)
        return ssl_nomem;
    if (ecode == 0)
    {
        snprintf(errbuf, SSL_ERR_LEN, libpq_gettext("no SSL error reported"));
        return errbuf;
    }
    errreason = ERR_reason_error_string(ecode);
    if (errreason != NULL)
    {
        strlcpy(errbuf, errreason, SSL_ERR_LEN);
        return errbuf;
    }
    snprintf(errbuf, SSL_ERR_LEN, libpq_gettext("SSL error code %lu"), ecode);
    return errbuf;
}

static void
SSLerrfree(char *buf)
{
    if (buf != ssl_nomem)
        free(buf);
}

/* ------------------------------------------------------------ */
/*                    SSL information functions                    */
/* ------------------------------------------------------------ */

int
PQsslInUse(PGconn *conn)
{
    if (!conn)
        return 0;
    return conn->ssl_in_use;
}

/*
 *    Return pointer to OpenSSL object.
 */
void *
PQgetssl(PGconn *conn)
{
    if (!conn)
        return NULL;
    return conn->ssl;
}

void *
PQsslStruct(PGconn *conn, const char *struct_name)
{
    if (!conn)
        return NULL;
    if (strcmp(struct_name, "OpenSSL") == 0)
        return conn->ssl;
    return NULL;
}

const char *const *
PQsslAttributeNames(PGconn *conn)
{
    static const char *const result[] = {
        "library",
        "key_bits",
        "cipher",
        "compression",
        "protocol",
        NULL
    };

    return result;
}

const char *
PQsslAttribute(PGconn *conn, const char *attribute_name)
{// #lizard forgives
    if (!conn)
        return NULL;
    if (conn->ssl == NULL)
        return NULL;

    if (strcmp(attribute_name, "library") == 0)
        return "OpenSSL";

    if (strcmp(attribute_name, "key_bits") == 0)
    {
        static char sslbits_str[10];
        int            sslbits;

        SSL_get_cipher_bits(conn->ssl, &sslbits);
        snprintf(sslbits_str, sizeof(sslbits_str), "%d", sslbits);
        return sslbits_str;
    }

    if (strcmp(attribute_name, "cipher") == 0)
        return SSL_get_cipher(conn->ssl);

    if (strcmp(attribute_name, "compression") == 0)
        return SSL_get_current_compression(conn->ssl) ? "on" : "off";

    if (strcmp(attribute_name, "protocol") == 0)
        return SSL_get_version(conn->ssl);

    return NULL;                /* unknown attribute */
}

/*
 * Private substitute BIO: this does the sending and receiving using
 * pqsecure_raw_write() and pqsecure_raw_read() instead, to allow those
 * functions to disable SIGPIPE and give better error messages on I/O errors.
 *
 * These functions are closely modelled on the standard socket BIO in OpenSSL;
 * see sock_read() and sock_write() in OpenSSL's crypto/bio/bss_sock.c.
 * XXX OpenSSL 1.0.1e considers many more errcodes than just EINTR as reasons
 * to retry; do we need to adopt their logic for that?
 */

#ifndef HAVE_BIO_GET_DATA
#define BIO_get_data(bio) (bio->ptr)
#define BIO_set_data(bio, data) (bio->ptr = data)
#endif

static BIO_METHOD *my_bio_methods;

static int
my_sock_read(BIO *h, char *buf, int size)
{
    int            res;

    res = pqsecure_raw_read((PGconn *) BIO_get_data(h), buf, size);
    BIO_clear_retry_flags(h);
    if (res < 0)
    {
        /* If we were interrupted, tell caller to retry */
        switch (SOCK_ERRNO)
        {
#ifdef EAGAIN
            case EAGAIN:
#endif
#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
            case EWOULDBLOCK:
#endif
            case EINTR:
                BIO_set_retry_read(h);
                break;

            default:
                break;
        }
    }

    return res;
}

static int
my_sock_write(BIO *h, const char *buf, int size)
{
    int            res;

    res = pqsecure_raw_write((PGconn *) BIO_get_data(h), buf, size);
    BIO_clear_retry_flags(h);
    if (res <= 0)
    {
        /* If we were interrupted, tell caller to retry */
        switch (SOCK_ERRNO)
        {
#ifdef EAGAIN
            case EAGAIN:
#endif
#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
            case EWOULDBLOCK:
#endif
            case EINTR:
                BIO_set_retry_write(h);
                break;

            default:
                break;
        }
    }

    return res;
}

static BIO_METHOD *
my_BIO_s_socket(void)
{// #lizard forgives
    if (!my_bio_methods)
    {
        BIO_METHOD *biom = (BIO_METHOD *) BIO_s_socket();
#ifdef HAVE_BIO_METH_NEW
        int            my_bio_index;

        my_bio_index = BIO_get_new_index();
        if (my_bio_index == -1)
            return NULL;
        my_bio_methods = BIO_meth_new(my_bio_index, "libpq socket");
        if (!my_bio_methods)
            return NULL;

        /*
         * As of this writing, these functions never fail. But check anyway,
         * like OpenSSL's own examples do.
         */
        if (!BIO_meth_set_write(my_bio_methods, my_sock_write) ||
            !BIO_meth_set_read(my_bio_methods, my_sock_read) ||
            !BIO_meth_set_gets(my_bio_methods, BIO_meth_get_gets(biom)) ||
            !BIO_meth_set_puts(my_bio_methods, BIO_meth_get_puts(biom)) ||
            !BIO_meth_set_ctrl(my_bio_methods, BIO_meth_get_ctrl(biom)) ||
            !BIO_meth_set_create(my_bio_methods, BIO_meth_get_create(biom)) ||
            !BIO_meth_set_destroy(my_bio_methods, BIO_meth_get_destroy(biom)) ||
            !BIO_meth_set_callback_ctrl(my_bio_methods, BIO_meth_get_callback_ctrl(biom)))
        {
            BIO_meth_free(my_bio_methods);
            my_bio_methods = NULL;
            return NULL;
        }
#else
        my_bio_methods = malloc(sizeof(BIO_METHOD));
        if (!my_bio_methods)
            return NULL;
        memcpy(my_bio_methods, biom, sizeof(BIO_METHOD));
        my_bio_methods->bread = my_sock_read;
        my_bio_methods->bwrite = my_sock_write;
#endif
    }
    return my_bio_methods;
}

/* This should exactly match openssl's SSL_set_fd except for using my BIO */
static int
my_SSL_set_fd(PGconn *conn, int fd)
{
    int            ret = 0;
    BIO           *bio;
    BIO_METHOD *bio_method;

    bio_method = my_BIO_s_socket();
    if (bio_method == NULL)
    {
        SSLerr(SSL_F_SSL_SET_FD, ERR_R_BUF_LIB);
        goto err;
    }
    bio = BIO_new(bio_method);
    if (bio == NULL)
    {
        SSLerr(SSL_F_SSL_SET_FD, ERR_R_BUF_LIB);
        goto err;
    }
    BIO_set_data(bio, conn);

    SSL_set_bio(conn->ssl, bio, bio);
    BIO_set_fd(bio, fd, BIO_NOCLOSE);
    ret = 1;
err:
    return ret;
}
